Sub

What AV software do you use?

What Vulnerability Scanners /do you use/ ?













Efficient and easy to use web services: XFire in practice

Efficient and easy to use web services: XFire in practice

Tomasz Sztelak

 Web services have proved the long-awaited solution for communication between distributed applications running on a variety of platforms and created using a variety of programming languages. The use of XML-based industry standards such as SOAP, WSDL and UDDI has made it possible to exchange data and services between applications running not only within such frameworks as J2EE or Microsoft .NET, but also applications written in practically any other language, whether compiled (for example C++, Pascal, Cobol) or interpreted (Perl, Python, PHP and others).

The rapid development of web service technologies was spurred by the fact that web services are loosely coupled with the applications they serve logic to, so adding a web service has very little effect on existing application components, making it possible to gradually integrate web services with existing functionality. The approach also allows existing capabilities to be served up using web services without costly modifications to the core system, and with a minimum risk of failure.

Web services provide a way to integrate disparate applications into one large distributed system, effectively eliminating the need to deploy costly middleware to translate between the various applications' communication methods. Large web-based corporations were quick to notice the benefits of using web services and start serving functionality using this technology - Google, Amazon, eBay, Yahoo and Allegro (largest Polish auction site) to name but a few.

The level of interest in the technology is best attested by the fact that over 50 000 programmers are currently registered in the Amazon web services program. The eBay program currently has over 8000 corporate and individual participants, with the 600 applications currently using the eBay API accounting for some 40% of all transactions.

Figure 1. Web service architecture

Problems with existing toolkits

A quick web search brings up a multitude of web service development tools. Some of the most popular include:

However, selecting a specific application to suit your needs is not an easy task. Each of the above makes it possible to serve up application logic as a web service, but each also has disadvantages that can make it cumbersome or impossible to use. To start with, let's try to define the features that any sensible development tool should have:

  • ease of use - applications can be developed faster (and therefore at a lower cost),

  • high performance - more clients can be served without having to invest in additional hardware,

  • consistent API - easier maintenance and increased development comfort,

  • low price - faster return on investment.

Unfortunately, most of the tools listed above fail in one or more of these areas. Small and medium businesses usually cannot afford to buy costly (though very good) commercial packages, such as WASP or Weblogic Server. The performance of several solutions is also questionable. The typical problem is that SOAP messages are processed using DOM, which usually involves first building a document to represent the XML content of the message and only then creating an application object corresponding to this data. This results not only in lower performance, but also considerable memory overhead, with the document tree frequently taking up many times the space of the data itself. Such performance issues apply to Axis 1.x (Axis 2 will use much faster stream-based XML processing using StAX) and JAX-RPC. Moreover, few commercial offerings shine on ease of use, often requiring complicated configuration and additional operations during installation, or providing awkward APIs that make it much harder to properly understand the tool and develop applications.

Figure 2. Service description in WSDL format

Why XFire?

The XFire project came about in response to problems with developing web services using available tools (including the ones listed above). The aim was to create a free toolkit with all the functionality of commercial solutions, but without their shortcomings. Here is how XFire scores on the features outlined above:

  • Low price - XFire is completely free and open source.

  • High performance - performance was a key requirement for the project, so the server architecture was based in its entirety on XML processing using the high-performance StAX model, allowing messages to be processed incrementally with minimum memory overhead. The project also features a purpose-built framework for mapping XML messages to Java objects, called Aegis, which is one of the fastest solutions of its type. Benchmarks have shown that, depending on SOAP message size, the XFire engine is 2 to 5 times faster than the market-leading Axis 1.x.

  • Ease of use - application success is largely dependent on the time required for development, so a key goal of the XFire project was ensuring maximum ease of use. The aim was achieved by introducing a simple and consistent architecture and making installation and configuration as simple as possible (though still allowing complex solutions to be deployed), as demonstrated by the examples that follow.


A web service in 5 minutes

We'll start by creating the server side. To create a web service, you'll need any application server (such as Tomcat 5.x, http://tomcat.apache.org), an XFire distribution package (xfire-all-1.0-M6.zip or later, available at http://xfire.codehaus.org/Download) and any Java IDE (such as Eclipse, http://www.eclipse.org). Deploying a web service requires a suitable application directory structure to be created, as shown in Listing 1. Now copy the following files to the application's library subdirectory (WEB-INF/lib): xfire-all-1.0-SNAPSHOT.jar, stax-api-1.0.jar, wsdl4j-1.4.jar, xerces-2.4.0.jar, xml-apis.jar, jdom-1.0.jar, annogen-0.1.0.jar, qdox-1.5.jar, xbean-spring-2.0-SNAPSHOT.jar, spring-1.2.4.jar, stax-1.1.2-dev.jar.

Listing 1. Application directory structure

xfire
- WEB-INF/
-classes/ - service classes
-META-INF/
-xfire/
-services.xml - service definition
-lib/ - libraries used by service code
web.xml

Initial configuration for XFire proceeds in a similar manner to other products of this sort, requiring the definition of a servlet to provide an access point for receiving client requests and passing them on to the relevant classes. The definition resides in the web.xml file in the application directory, as shown in Listing 2. Assuming that you're running Tomcat on its default port and the application is called XFire, all client requests sent to http://localhost:8080/xfire/services will be treated as web service requests and passed to the XFire servlet.

Listing 2. The web.xml file

xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app   PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>

<servlet>
<servlet-name>XFireServletservlet-name>
<display-name>XFire Servletdisplay-name>
<servlet-class>org.codehaus.xfire.transport.http.XFireConfigurableServletservlet-class>
servlet>

<servlet-mapping>
<servlet-name>XFireServletservlet-name>
<url-pattern>/services/*url-pattern>
servlet-mapping>
web-app>

Next, we need to provide an interface (Listing 3) and implementation (Listing 4) for the actual application class.

Listing 3. Class interface for the sample application

package pl.sdj.petstore;
public interface PetStore {
// buy a pet with the specified name
long buyPet(String name);
// check order status
String getOrderStatus(long orderId);
}

Listing 4. Class implementation for the sample application

package pl.sdj.petstore.impl;
import pl.sdj.petstore.PetStore;
public class PetStoreImpl implements PetStore {
public String getOrderStatus(long orderId) {
// "mouse" is the only pet in stock
if (orderId == "mouse".hashCode()) {
return "ordered";
}
return "unknown";
}
public long buyPet(String name) {
// return hashcode as order id
return (name == null ? -1 : name.hashCode());
}
}

As a final configuration step, we need to indicate which application classes should be served up as web services. This is done by creating the file services.xml in the META-INF/xfire directory, with the content shown in Listing 5. The elements used in the file are as follows:

  • name - access name for the service (e.g. PetStore),

  • namespace - service namespace,

  • serviceClass - interface class containing service method definitions,

  • implementationClass - service implementation class.

Listing 5. Service configuration in the META-INF/xfire/services.xml file

<beans xmlns="http://xfire.codehaus.org/config/1.0">
<service>
<name>PetStorename>
<namespace>http://www.sdj.plnamespace>
<serviceClass>pl.sdj.petstore.PetStoreserviceClass>
<implementationClass>pl.sdj.petstore.impl.PetStoreImplimplementationClass>
service>
beans>

Once the application has been built and deployed on the server, we should have a fully functional web service. To check if everything is as it should be, open the URL http://localhost:8080/xfire/services/PetStore?wsdl in a web browser and you should see a WSDL description of the web service.

As you can see, a fully functional web service can be deployed with just a few lines of configuration. Simple though they are, the XFire configuration mechanisms actually provide control of virtually all aspects of application operation. Listing 6 shows the elements available within the basic configuration framework.

Listing 6. Configuration file structure

<beans xmlns="http://xfire.codehaus.org/config/1.0">

<xfire>
..<inHandlers>    <handler handlerClass=""/> inHandlers>
<outHandlers>   <handler handlerClass=""/> outHandlers>
<faultHandlers> <handler handlerClass=""/> faultHandlers>
xfire>

<service>

<name />

<serviceClass />

<implementationClass/>

<namespace />

<serviceFactory/> 

<style/>

<soapVersion />

<properties>
<property key="key">valueproperty>
properties>

<inHandlers>    <handler handlerClass="" />inHandlers>
<outHandlers>   <handler handlerClass="" />outHandlers>
<faultHandlers> <handler handlerClass="" />faultHandlers>
service>
beans>

Adding functionality

Now that we know how a web service works and what configuration parameters are available, it would be a good idea to extend its functionality a bit, for example by allowing only clients from a specific IP to call the web service methods. This capability can be added through the use of handler classes, executed each time a service method call is received. Handler objects can be registered for all incoming messages (using inHandler objects to handle method calls), outgoing messages (using outHandler objects to handle method call results) and error notifications (using faultHandler objects to pass information about any errors during a method call). Handler classes can be registered both for specific services and globally for all existing services. Handlers can also be placed at various phases of message processing (defined as Phase objects), allowing handler logic to be hooked into the parsing phase (PARSE), into the service selection phase (DISPATCH), just before the method call (PRE-INVOKE) or at any other predefined point. This means that additional message-related functionality can easily be added, including SOAP message encryption or signing, user authentication or service API version control.

Listing 7 presents a sample handler class. The invoke() method is the most important part, as it is called for each message. In this example, the method retrieves the IP of the calling client from the HTTP request (an HTTPServletRequest object) and compares it to the permitted IP for the web service, as defined in the configuration file. The getPhase() method returns information on the phase of the message processing chain that the class should apply to (incoming message processing involves the following phases: TRANSPORT, PARSE, PRE_DISPATCH, DISPATCH, POLICY, USER, PRE_INVOKE, SERVICE).

Listing 7. Sample handler class implementation

package pl.sdj.handlers;
import org.codehaus.xfire.MessageContext;
import org.codehaus.xfire.handler.AbstractHandler;
import org.codehaus.xfire.transport.http.XfireServletController;
public class IPRestrictionHandler extends AbstractHandler {
public void invoke(MessageContext ctx) throws Exception {
// get remote client IP
String remoteIp = XfireServletController.getRequest().getRemoteAddr();
// get permitted client IP
String allowedIP = (String) ctx.getService().getProperty("allowedIP");
System.out.println("Request to call method "+ctx.getInMessage().getAction()+" from IP "+remoteIp);
// compare IPs
if( !remoteIp.equals(allowedIP)){
// if the IP are different, end message processing
throw new XFireException("Your IP is not permitted");
}
}
// specifies the handler's place in the processing chain
public String getPhase()
{
return Phase.USER;
}
}

Note that testing handler code within the IDE requires the servletapi-2.3.jar file from the XFire package to be added to the application libraries. Now we simply register the handler class for our service by adding a suitable handler element to the services.xml file, as shown in Listing 8. From now on, we can be sure that unauthorised clients will not be able to access the functionality of our web service.

Listing 8. Extended service configuration

<beans xmlns="http://xfire.codehaus.org/config/1.0">
<service>
<name>PetStorename>
<namespace>http://www.sdj.plnamespace>
<serviceClass>pl.sdj.petstore.PetStoreserviceClass>
<implementationClass>pl.sdj.petstore.impl.PetStoreImplimplementationClass>    
<properties>
<property key="allowedIP">127.0.0.1property>
properties>
<inHandlers>
<handler handlerClass="pl.sdj.handlers.IPRestrictionHandler" />
inHandlers> 
service>
beans>

Creating a web service client

We will now build a simple command-line client application for our web service. To run the client, you will need the following libraries from the XFire distribution package: commons-discovery-0.2.jar, commons-logging-1.0.4.jar, log4j-1.2.8.jar, wsdl4j-1.5.1.jar, jdom-1.0.jar, servletapi-2.3.jar, stax-1.1.2-dev.jar, stax-api-1.0.jar, xerces-2.4.0.jar, xfire-all-1.0-SNAPSHOT.jar, xml-apis.jar, javamail-1.3.2.jar, commons-httpclient-3.0-rc3.jar, commons-codec-1.3.jar, as well as the actual client application class using the PetStore service API, as shown in Listing 9.

Listing 9. Service client implementation

public class XfireClient
{
// service name
private static final String NAME = "PetStore";
// service namespace
private static final String NAMESPACE ="http://www.sdj.pl";
// service interface
private static final Class SERVICECLASS = PetStore.class;
// service URL
private static final String SERVICE_ADDRESS = "http://127.0.0.1:8080/xfire/services/PetStore";
// service instance
private PetStore service;
public void init()
{
// service properties definition
Service serviceModel = new ObjectServiceFactory().create(SERVICECLASS, NAME, NAMESPACE, null);
try
{
// associate the local service instance with the remote service
service = (PetStore) new XfireProxyFactory().create(serviceModel, SERVICE_ADDRESS);
}
catch (MalformedURLException e)
{
throw new RuntimeException("Wrong URL: "+e.getMessage());
}
}
public void buyPet() throws Exception
{
System.out.println("Buying 'mouse'");
long orderId = service.buyPet("mouse");
System.out.println("Order ID: " + orderId );
String status = service.getOrderStatus(orderId);
System.out.println("Order status: " + status);
}
public static void main(String[] args)   throws Exception
{
XFireClient client = new XFireClient();
client.init();
client.buyPet();
}
}

The most important part of the client class is the init() method, which creates a service object whose API mirrors that of the remote service object. The local object must be initialised using the exact same values as the server-side one, so the factory method call should include the following element values taken from services.xml:

ObjectServiceFactory().create(, , , properties);

The properties argument allows additional parameters to be specified, including the SOAP protocol version or message generation method.

Now we need to create a proxy (an XFireProxy object) to delegate method calls to the remote service. The proxy factory method call takes two arguments: the local service object and the URL of the remote service to be invoked. From now on, any calls to the local service object will yield the same results as method calls for the corresponding remote service. Running the client class from the command line should give the result shown in Listing 10.

Listing 10. Client call res